# Classe base para implementação dos métodos complexos de acesso a dados no server-side.
define [
  'chaplin'
  'lib/dao_factory'
  'lib/enums'
  'lib/ajax_setup'
  ], (Chaplin, DaoFactory, Enums, ajaxSetup) ->

  'use strict'

  class Dao

    # Url base para este dao
    baseUrl: '/'
    # Classe para os models que serão criados a partir desse Dao
    # O método @fetchModel irá utilizar
    modelClass: null
    # Class para as collections que serão criadas a partir desse Dao
    collectionClass: null

    #### Construtor(baseUrl='/')
    constructor: (baseUrl='/') ->
      @setBaseUrl baseUrl

    #### @setBaseUrl(@baseUrl)
    # Seta a url base para este Dao
    setBaseUrl: (@baseUrl) ->
      @baseUrl = @baseUrl + "/" if @baseUrl.charAt(@baseUrl.length - 1) isnt "/"

    #### @removeWhitespaces
    removeWhitespaces: (data) ->
      # Se o valor for um objeto
      if _.isObject(data)
        for key, value of data
          # Apensar convertemos os valores do tipo string
          if _.isString(value)
            data[key] = value.replace( /\s\s+/g, ' ' )

    #### @fetchCollection(type = "GET", url = "", params = [], data = {})
    # Faz o fetch de uma collection nos parâmetros especificados.
    #
    # * `type`
    #   O tipo do request. GET, POST, PUT ou DELETE
    # * `url`
    #   A url que será requisitada. Caso algum placeholder {0-9} seja encontrado na URL,
    #   o placeholder será substituído pela índice respectivo no array de parâmetros enviados
    #   como atributo desse método.
    # * `params`
    #   Lista de parâmetros que serão substituídos na URL
    #
    # Exemplo:
    # ```
    # @fetchCollection 'GET', 'usuarios/{0}/items', [1]
    # ```
    fetchCollection: (type = "GET", url = "", params = [], data = {}) ->
      # Cria uma nova promise para ser retornada durante o request
      promise = $.Deferred()

      # Remove whitespace
      @removeWhitespaces(data)

      # Cria uma requisição ajax com a url que será requisitada
      ajaxPromise = $.ajax {
        url: @constructUrl url, params
        type: type
        dataType: 'json'
        data: data
      }

      ajaxPromise
        .fail(@defaultPromiseError(promise))
        # Caso o request funcione corretamente, devemos criar uma collection com o json
        # que foi retornado do request e resolver a promise criada com a collection criada
        .done (json, status, ajaxObj) =>
          collection = new @collectionClass json
          promise.resolve.apply @, [collection, ajaxObj]

      # Retorna a promise criada para dar a oportunidade do desenvolvedor fazer o callback
      # de acordo com o retorno do request.
      promise

    defaultPromiseError: (promise) =>
      func = (error) =>
        ajaxSetup.error.apply @, arguments
        promise.reject.apply @, arguments

    #### @fetchModel(type = "GET", url = "", params = [], data = {})
    # Faz o fetch de um model nos parâmetros especificados.
    #
    # * `type`
    #   O tipo do request. GET, POST, PUT ou DELETE
    # * `url`
    #   A url que será requisitada. Caso algum placeholder {0-9} seja encontrado na URL,
    #   o placeholder será substituído pela índice respectivo no array de parâmetros enviados
    #   como atributo desse método.
    # * `params`
    #   Lista de parâmetros que serão substituídos na URL
    #
    # Exemplo:
    # ```
    # @fetchModel 'GET', 'usuarios/{0}', [1]
    # ```
    fetchModel: (type = "GET", url = "", params = [], data = {}) ->
      if _.isObject(url)
        data = url
        url = ""

      # Remove whitespace
      @removeWhitespaces(data)

      promise = $.Deferred()
      ajaxPromise = $.ajax {
        url: @constructUrl url, params
        type: type
        dataType: 'json'
        data: data
      }

      ajaxPromise
        .fail(@defaultPromiseError(promise))
        .done (json, status, ajaxObj) =>
          model = new @modelClass json
          promise.resolve.apply @, [model, ajaxObj]

      promise

    #### @fetchRaw(type = "GET", url = "", params = [], data = {})
    # Faz o fetch de uma URL e retorna o resultado sem converter para um model ou collection.
    #
    # * `type`
    #   O tipo do request. GET, POST, PUT ou DELETE
    # * `url`
    #   A url que será requisitada. Caso algum placeholder {0-9} seja encontrado na URL,
    #   o placeholder será substituído pela índice respectivo no array de parâmetros enviados
    #   como atributo desse método.
    # * `params`
    #   Lista de parâmetros que serão substituídos na URL
    #
    # Exemplo:
    # ```
    # @fetchModel 'GET', 'usuarios/{0}', [1]
    # ```
    fetchRaw: (type = "GET", url = "", params = [], data = {}, options={}) ->
      if _.isObject(url)
        data = url
        url = ""

      # Remove whitespace
      @removeWhitespaces(data)

      if _.indexOf(['PUT', 'DELETE'], type) >= 0 and options.contentType != 'application/json; charset=UTF-8'
        oldType = type
        type = 'POST'
        data['_method'] = oldType

      promise = $.Deferred()
      ajaxOptions = {
        url: this.constructUrl url, params
        type: type
        dataType: 'json'
        data: data
      }

      ajaxOptions = _.extend ajaxOptions, options

      if ajaxOptions.contentType is 'application/json; charset=UTF-8'
        ajaxOptions.data = window.JSON.stringify ajaxOptions.data

      ajaxPromise = $.ajax ajaxOptions

      ajaxPromise
        .fail(@defaultPromiseError(promise))
        .done (json, status, ajaxObj) =>
          promise.resolve.apply @, [json, ajaxObj]

      promise

    #### @listar()
    # Lista todas as entidades
    listar: () ->
      @fetchCollection "GET"

    #### @remover(id)
    # Remove uma entidade pelo seu id
    remover: (id) ->
      @fetchModel "DELETE", "{0}", [id]

    #### @atualizar(id, data)
    # Atualiza uma entidade pelo seu id
    atualizar: (id, data) ->
      @fetchModel "PUT", "{0}", [id], data

    #### @criar(data)
    # Cria uma entidade
    criar: (data) ->
      @fetchModel "POST", "{0}", null, data

    #### @ler(id)
    # Lê uma entidade pelo seu id
    ler: (id) ->
      @fetchModel "GET", "{0}", [id]

    #### @constructUrl(url, params)
    # Constrói uma url substituindo os seus parâmetros na url.
    constructUrl: (url, params) ->
      replaceParam = (param, index) ->
        reg = new RegExp "\\{" + index + "\\}"
        url = url.replace reg, param

      replaceParam param, paramIndex for param, paramIndex in params
      url = @baseUrl + url if url.indexOf('http://') < 0

      return url
